Do: almost finished. Just make a couple of these. See how to hide
code chunks for big ones of groups at end. See if there is a simple
markdown solution to seeing dfsummary or remove the histograms.
Campus-Level Data for 2019:
This example highlights my data wrangling and visualization skills in
an exploratory analysis of racial/ethnic inequity in public schools in
Texas. This was a pet project that I created to satisfy my own curiosity
on the topic and to get to know the publicly available data related to
education in Texas. I clean, validate, and join several data sets with
the end goal of exploring the relationship between race/ethnicity,
economic disparity, and educational outcomes in Texas public schools.
This page provides the steps required to go from several raw public data
sets to produce the following interactive visualization, as well as
several others, using the R packages listed below:
final example here using full code in echo = F chunk.
Load Packages
library(tidyverse)
library(readxl)
library(plotly)
library(gapminder)
library(kableExtra)
library(summarytools)
Percent of Students Meeting Grade-Level by Race/Ethnic
Categories
For educational outcomes, I use the publicly available Texas
Academic Performance Report (TAPR) data for the year 2019. This is a
very large data set that that I have subsetted to focus on the
percentage of students in three racial/ethnic categories (variable:
Student Group) who meet the expectations of their
respective grade levels (variable: meets_level_pct). I also
include the numeric campus identifier cid and the
respective denominators for each percentile value (variable:
meets_denom), which informs the absolute number of students
from each category at the school. I use this value to weight the
observations in the Loess smoothing function and to inform the size
parameter in the plots. For the original variable names, see the TAPR
Codebook.
As described in the codebook
for the data set, student groups’ percentile values with very small
denominators are masked (coded as negative placeholder values of “-1”)
in order to preclude the possibility of identification of individuals in
the data. I exclude these masked values from the data set, as well as
any school that reports missing data for all three student groups.
Denominators of the second smallest student group for each school are
also masked with a unique placeholder (“-3”) when the smallest group
requires masking. I replace this set of masked values with a reasonable
proxy of half the size of the next largest group in the data.
This code does the following:
- Read in subsetted TAPR Data
- Filter schools with all missing values
- Pivot the data to include (up to) three observations per school,
corresponding to the three percentile scores
meets_level_pct of students in each racial/ethnic category
that meet the grade-level expectations.
- Create category names for student groups:
Student Group
and group_denom
- Remove masked data for percentiles and recode the masked denominator
with the proxy.
- Create an additional variable
ln_meets_denom of logged
group size for use as weight and size in the plots.
The result is
campus_2019 <- read.csv("docs/TAPR_2019_subset.csv") %>%
transmute(
cid = CAMPUS,
`African American` = CDB00A001219R,
`Hispanic` = CDH00A001219R,
`White` = CDW00A001219R,
aa_total = CDB00A001019D,
h_total = CDH00A001019D,
w_total = CDW00A001019D) %>%
# Filter out schools with no data (~ 10% of campuses)
filter(!is.na(`African American`) | !is.na(`Hispanic`) | !is.na(`White`))
data <- cbind(
pivot_longer(campus_2019 %>% select(1:4), cols = 2:4, names_to = "Student Group", values_to = "Meets Grade Level (%)"),
pivot_longer(campus_2019 %>% select(5:7), cols = 1:3, names_to = "group_denom", values_to = "Size of Student Group at School")) %>%
group_by(cid) %>%
mutate(
`Meets Grade Level (%)` = ifelse(`Meets Grade Level (%)` == -1, NA, `Meets Grade Level (%)`), # rate = -1 is masked data and unusable
`Size of Student Group at School` = ifelse(`Size of Student Group at School` == -1, NA, # denominator = -1 is masked data and unusable
`Size of Student Group at School`),
`Size of Student Group at School` = ifelse(`Size of Student Group at School` == -3, # denominator = -3 is second smallest group
nth(`Size of Student Group at School`, 2, order_by = `Size of Student Group at School`) / 2,
`Size of Student Group at School`), # recover with reasonable proxy of half largest group
ln_meets_denom = log(`Size of Student Group at School`)) %>% # log will be better for size parameter
ungroup()
data[1:9,] %>%
kbl(caption = "Structure of the Data Set: Showing the First Three Schools") %>%
kable_paper("hover", full_width = F)
Structure of the Data Set: Showing the First Three Schools
|
cid
|
Student Group
|
Meets Grade Level (%)
|
group_denom
|
Size of Student Group at School
|
ln_meets_denom
|
|
1902001
|
African American
|
71
|
aa_total
|
7
|
1.945910
|
|
1902001
|
Hispanic
|
58
|
h_total
|
19
|
2.944439
|
|
1902001
|
White
|
67
|
w_total
|
180
|
5.192957
|
|
1902041
|
African American
|
0
|
aa_total
|
5
|
1.609438
|
|
1902041
|
Hispanic
|
48
|
h_total
|
31
|
3.433987
|
|
1902041
|
White
|
60
|
w_total
|
282
|
5.641907
|
|
1902103
|
African American
|
50
|
aa_total
|
6
|
1.791759
|
|
1902103
|
Hispanic
|
75
|
h_total
|
12
|
2.484907
|
|
1902103
|
White
|
62
|
w_total
|
330
|
5.799093
|
Campus-Level Poverty Data
For campus-level poverty statistics, I use the public data available
at the Texas Education Agency’s (TEA) school
data visualizer, which includes an extensive set of campus-level
attributes. I am primarily interested in the percentage of students from
economically disadvantaged backgrounds, measured as the percentage of
students “…Students eligible for free or reduced-price lunch or other
public assistance as reported on the PEIMS October snapshot” (see
definitions). Unfortunately, the source does not reveal precisely
which year this data comes from! School-level poverty is a relatively
stable attribute, though, so this is not a huge concern.
The challenge is that this data does not include the campus ID as a
stand-alone variable. I must first extract it from a longer string to
create a comparable cid variable that can be used to join
this data set to the first one. Because there is no universal pattern to
parse this string, I have to split on three different patterns and
extract the rightmost element in order to recover all the campus
identifiers. This is because some schools have more than one set of the
” (” pattern on the right-hand side of the “||” pattern. Each
school <- read_excel("docs/school.xlsx")
school <- school %>%
transmute(
Campus = `Campus or District`,
`Eco. Disadvantaged (%)` = `% Eco Disadvantaged`,
`Campus Type` = factor(`Entity Type`),
enrollment_public = Enrollment) %>%
mutate(
n = str_split(Campus, " \\|\\|", simplify = TRUE)[,2], # First split on "||"
nn = str_split(n, " \\(", simplify = TRUE)[,2], # Next splot on " ("
nnn = str_split(n, " \\(", simplify = TRUE)[,3], # A few have an additional " ("
cid = as.numeric(gsub("\\D+", "", nn)),
cid = ifelse(is.na(cid), as.numeric(gsub("\\D+", "", nnn)), cid)) %>%
select(-starts_with("n"))
school[1:5,] %>%
kbl(caption = "Structure of the Poverty Data after Recovering the Campus Identifier") %>%
kable_paper("hover", full_width = F)
Structure of the Poverty Data after Recovering the Campus Identifier
|
Campus
|
Eco. Disadvantaged (%)
|
Campus Type
|
enrollment_public
|
cid
|
|
CAYUGA H S || CAYUGA ISD (001902001)
|
34.9
|
High School
|
166
|
1902001
|
|
CAYUGA MIDDLE || CAYUGA ISD (001902041)
|
40.6
|
Middle & Jr. High School
|
133
|
1902041
|
|
CAYUGA EL || CAYUGA ISD (001902103)
|
38.1
|
Elementary School
|
236
|
1902103
|
|
ELKHART H S || ELKHART ISD (001903001)
|
47.4
|
High School
|
344
|
1903001
|
|
ELKHART MIDDLE || ELKHART ISD (001903041)
|
45.1
|
Middle & Jr. High School
|
268
|
1903041
|
Codes: School, District, Region, County
This data set from TEA includes the codes that can be used for
geographic identification in the plots.
codes <- read_csv("docs/school and district.csv")
codes <- codes %>%
transmute(
cid = as.numeric(gsub("[^[:alnum:] ]", "", `School Number`)),
district = as.numeric(gsub("[^[:alnum:] ]", "", `District Number`)),
county = as.numeric(gsub("[^[:alnum:] ]", "", `County Number`)),
region = as.numeric(gsub("[^[:alnum:] ]", "", `ESC Region Served`)))
Join Data and See Descriptive Statistics
Taking the TAPR 2019 data as the base set, I match approximately
98.3% of campuses with the TEA school-level data by their campus id
(cid). For parsimony, I exclude those %1.7% campuses that
do not exist in both data sets. We can see that the masking and missing
data results in approximately 10% of missing data from the TAPR 2019
data; however, due to the logic of how the masking is coded, those
masked observations are likely to be smaller, relatively more
homogeneous schools, otherwise, they would not have very small absolute
numbers of students from any of the three racial/ethnic backgrounds.
#campus <- left_join(data, school, by = "cid") %>% left_join(codes, by = "cid")
campus <- left_join(school, codes, by = "cid") %>% right_join(data, by = "cid") %>%
filter(!is.na(Campus))
dfSummary(campus,
plain.ascii = FALSE,
style = "grid",
graph.magnif = 0.75,
valid.col = FALSE,
tmp.img.dir = "/tmp",
silent = TRUE)
Exploring the Relationship between Race/Ethnicity, Poverty, and
Education Outcomes
Here I write a function to build plotly interactive
visualizations (hover over the image to get point-level data).
School-level data is plotted on the x-axis based on poverty levels
Eco. Disadvanted (%). School-level data disaggregated by
race/ethnicity on the y-axis to show each racial/ethnic groups’s
academic performance as Meets Grade Level (%). I write a
custom function that takes the data and two filtering parameters to
slice the full data set:
campus %>% filter('Campus Type' == {{type}} & region == {{r}}).
I pass this function to lapply() to generate any or all
combinations of region and Campus Type (up to
)
r <- 1:20
plots <- function(data, r, type) {
t <- ggplot(data = campus %>% filter(`Campus Type` == {{type}} & region == {{r}}),
aes(label = `Campus`, label2 = `Size of Student Group at School`, x = `Eco. Disadvantaged (%)`, y = `Meets Grade Level (%)`)) +
geom_line(alpha = .3, aes(group = cid)) +
geom_point(alpha = .4, aes(size = `Size of Student Group at School`, color = `Student Group`)) +
geom_smooth(size = 2, method = loess, color = "black", se = FALSE, aes(group = `Student Group`)) +
geom_smooth(size = 1.8, method = loess, se = FALSE, aes(color = `Student Group`)) +
guides(size = FALSE) +
theme(legend.position = "bottom") +
xlim(c(0,100)) + ylim(c(0,100)) +
ggtitle(paste("Region", {{r}}, {{type}}, "Performance by Race/Ethnicity and Poverty", sep = " "))
t[[r]] <- ggplotly(t, tooltip = c("label", "label2", "x", "y"))
#print(t[[r]])
#return(t[[r]])
}
# This works in R but not for markdown to html
# for (i in 1:20){
# plots(campus, i)
# }
gg_hs <- lapply(r, plots, data = campus, type = "High School")
gg_es <- lapply(r, plots, data = campus, type = "Elementary School")
[[1]]
[[2]]
[[3]]
[[4]]
[[5]]
[[6]]
[[7]]
[[8]]
[[9]]
[[10]]
[[11]]
[[12]]
[[13]]
[[14]]
[[15]]
[[16]]
[[17]]
[[18]]
[[19]]
[[20]]
subplot(gg_hs[[10]], gg_es[[10]], gg_hs[[20]], gg_es[[20]], nrows = 2)
htmltools::tagList(list(gg_hs[[10]], gg_es[[10]], gg_hs[[20]], gg_es[[20]]))
htmltools::tagList(list(gg_hs[[1]], gg_hs[[3]], gg_es[[1]], gg_es[[3]]))
Private Schools Data 2018 - 2019
private <- read_excel("docs/private schools 2018-2019.xlsx")
private <-private %>%
filter(!`Grade High` %in% c("Pre-K", "Early Education") & Closed == FALSE) %>%
transmute(
district = as.numeric(`District Number`),
county = as.numeric(`County Number`),
region = as.numeric(`Region Name`),
enrollment_private = as.numeric(Enrollment))
private_region <- private %>%
group_by(region) %>%
summarise(
private_enrollment_region = sum(enrollment_private, na.rm = TRUE)) %>%
ungroup()
public_region <- school %>%
left_join(codes, by = "cid") %>%
group_by(region) %>%
summarise(
public_enrollment_region = sum(enrollment_public, na.rm = TRUE)) %>%
ungroup()
region <- left_join(private_region, public_region, by = "region")
region <- region %>%
mutate(
`Total Enrollment` = private_enrollment_region + public_enrollment_region,
`Private Students in Region (%)` = 100 * ( private_enrollment_region / `Total Enrollment`),
Region_lab = paste("Region", region, sep = " "),
Region = factor(region, ordered = TRUE, labels = Region_lab)) %>%
arrange(desc(`Private Students in Region (%)`)) %>%
mutate(
Region_lab = paste("Region", region, sep = " "),
`Region Ordered` = factor(region, levels = region, labels = paste("Region", region, sep = " ")))
#rm(private, private_region, public_region)
ggplotly(ggplot(region, aes(x = log(`Total Enrollment`), y = `Private Students in Region (%)`, label = region)) +
geom_smooth(alpha = .4, method = lm) +
geom_text(size = 5))
## `geom_smooth()` using formula 'y ~ x'
ggplotly(ggplot(region %>% filter(!region %in% c(1, 3)), aes(x = log(`Total Enrollment`), y = `Private Students in Region (%)`, label = region)) +
geom_smooth(alpha = .4, method = lm) +
geom_text(size = 5))
## `geom_smooth()` using formula 'y ~ x'
p1 <- ggplotly(ggplot(region, aes(x = `Private Students in Region (%)`, y = Region)) +
geom_col())
p2 <- ggplotly(ggplot(region, aes(x = `Private Students in Region (%)`, y = `Region Ordered`)) +
geom_col())
subplot(p1, p2, margin = 0.07)
LS0tDQp0aXRsZTogIkV4LiAxIFRleGFzIFB1YmxpYyBTY2hvb2xzIg0KDQotLS0NCg0KRG86IGFsbW9zdCBmaW5pc2hlZC4gSnVzdCBtYWtlIGEgY291cGxlIG9mIHRoZXNlLiBTZWUgaG93IHRvIGhpZGUgY29kZSBjaHVua3MgZm9yIGJpZyBvbmVzIG9mIGdyb3VwcyBhdCBlbmQuIFNlZSBpZiB0aGVyZSBpcyBhIHNpbXBsZSBtYXJrZG93biBzb2x1dGlvbiB0byBzZWVpbmcgZGZzdW1tYXJ5IG9yIHJlbW92ZSB0aGUgaGlzdG9ncmFtcy4NCg0KIyBDYW1wdXMtTGV2ZWwgRGF0YSBmb3IgMjAxOTogDQoNClRoaXMgZXhhbXBsZSBoaWdobGlnaHRzIG15IGRhdGEgd3JhbmdsaW5nIGFuZCB2aXN1YWxpemF0aW9uIHNraWxscyBpbiBhbiBleHBsb3JhdG9yeSBhbmFseXNpcyBvZiByYWNpYWwvZXRobmljIGluZXF1aXR5IGluIHB1YmxpYyBzY2hvb2xzIGluIFRleGFzLiBUaGlzIHdhcyBhIHBldCBwcm9qZWN0IHRoYXQgSSBjcmVhdGVkIHRvIHNhdGlzZnkgbXkgb3duIGN1cmlvc2l0eSBvbiB0aGUgdG9waWMgYW5kIHRvIGdldCB0byBrbm93IHRoZSBwdWJsaWNseSBhdmFpbGFibGUgZGF0YSByZWxhdGVkIHRvIGVkdWNhdGlvbiBpbiBUZXhhcy4gSSBjbGVhbiwgdmFsaWRhdGUsIGFuZCBqb2luIHNldmVyYWwgZGF0YSBzZXRzIHdpdGggdGhlIGVuZCBnb2FsIG9mIGV4cGxvcmluZyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gcmFjZS9ldGhuaWNpdHksIGVjb25vbWljIGRpc3Bhcml0eSwgYW5kIGVkdWNhdGlvbmFsIG91dGNvbWVzIGluIFRleGFzIHB1YmxpYyBzY2hvb2xzLiBUaGlzIHBhZ2UgcHJvdmlkZXMgdGhlIHN0ZXBzIHJlcXVpcmVkIHRvIGdvIGZyb20gc2V2ZXJhbCByYXcgcHVibGljIGRhdGEgc2V0cyB0byBwcm9kdWNlIHRoZSBmb2xsb3dpbmcgaW50ZXJhY3RpdmUgdmlzdWFsaXphdGlvbiwgYXMgd2VsbCBhcyBzZXZlcmFsIG90aGVycywgdXNpbmcgdGhlIFIgcGFja2FnZXMgbGlzdGVkIGJlbG93Og0KDQpmaW5hbCBleGFtcGxlIGhlcmUgdXNpbmcgZnVsbCBjb2RlIGluIGVjaG8gPSBGIGNodW5rLg0KDQojIyMgTG9hZCBQYWNrYWdlcw0KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHJlYWR4bCkNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShnYXBtaW5kZXIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KHN1bW1hcnl0b29scykNCmBgYA0KDQojIyMgUGVyY2VudCBvZiBTdHVkZW50cyBNZWV0aW5nIEdyYWRlLUxldmVsIGJ5IFJhY2UvRXRobmljIENhdGVnb3JpZXMNCg0KRm9yIGVkdWNhdGlvbmFsIG91dGNvbWVzLCBJIHVzZSB0aGUgcHVibGljbHkgYXZhaWxhYmxlIFtUZXhhcyBBY2FkZW1pYyBQZXJmb3JtYW5jZSBSZXBvcnQgKFRBUFIpIGRhdGFdKGh0dHBzOi8vcnB0c3ZyMS50ZWEudGV4YXMuZ292L3BlcmZyZXBvcnQvdGFwci8yMDE5L2Rvd25sb2FkL0Rvd25sb2FkRGF0YS5odG1sKSBmb3IgdGhlIHllYXIgMjAxOS4gVGhpcyBpcyBhIHZlcnkgbGFyZ2UgZGF0YSBzZXQgdGhhdCB0aGF0IEkgaGF2ZSBzdWJzZXR0ZWQgdG8gZm9jdXMgb24gdGhlIHBlcmNlbnRhZ2Ugb2Ygc3R1ZGVudHMgaW4gdGhyZWUgcmFjaWFsL2V0aG5pYyBjYXRlZ29yaWVzICh2YXJpYWJsZTogYFN0dWRlbnQgR3JvdXBgKSB3aG8gbWVldCB0aGUgZXhwZWN0YXRpb25zIG9mIHRoZWlyIHJlc3BlY3RpdmUgZ3JhZGUgbGV2ZWxzICh2YXJpYWJsZTogYG1lZXRzX2xldmVsX3BjdGApLiBJIGFsc28gaW5jbHVkZSB0aGUgbnVtZXJpYyBjYW1wdXMgaWRlbnRpZmllciBgY2lkYCBhbmQgdGhlIHJlc3BlY3RpdmUgZGVub21pbmF0b3JzIGZvciBlYWNoIHBlcmNlbnRpbGUgdmFsdWUgKHZhcmlhYmxlOiBgbWVldHNfZGVub21gKSwgd2hpY2ggaW5mb3JtcyB0aGUgYWJzb2x1dGUgbnVtYmVyIG9mIHN0dWRlbnRzIGZyb20gZWFjaCBjYXRlZ29yeSBhdCB0aGUgc2Nob29sLiBJIHVzZSB0aGlzIHZhbHVlIHRvIHdlaWdodCB0aGUgb2JzZXJ2YXRpb25zIGluIHRoZSBMb2VzcyBzbW9vdGhpbmcgZnVuY3Rpb24gYW5kIHRvIGluZm9ybSB0aGUgc2l6ZSBwYXJhbWV0ZXIgaW4gdGhlIHBsb3RzLiBGb3IgdGhlIG9yaWdpbmFsIHZhcmlhYmxlIG5hbWVzLCBzZWUgdGhlIFtUQVBSIENvZGVib29rXShodHRwczovL3JwdHN2cjEudGVhLnRleGFzLmdvdi9wZXJmcmVwb3J0L3RhcHIvMjAxOS9kb3dubG9hZC9jYW1wc3RhYXIyYi5odG1sKS4gIA0KDQpBcyBkZXNjcmliZWQgaW4gdGhlIFtjb2RlYm9vayBmb3IgdGhlIGRhdGEgc2V0XShodHRwczovL3JwdHN2cjEudGVhLnRleGFzLmdvdi9wZXJmcmVwb3J0L3RhcHIvMjAxOS9kb3dubG9hZC9jYW1wc3RhYXIyYi5odG1sKSwgc3R1ZGVudCBncm91cHMnIHBlcmNlbnRpbGUgdmFsdWVzIHdpdGggdmVyeSBzbWFsbCBkZW5vbWluYXRvcnMgYXJlIG1hc2tlZCAoY29kZWQgYXMgbmVnYXRpdmUgcGxhY2Vob2xkZXIgdmFsdWVzIG9mICItMSIpIGluIG9yZGVyIHRvIHByZWNsdWRlIHRoZSBwb3NzaWJpbGl0eSBvZiBpZGVudGlmaWNhdGlvbiBvZiBpbmRpdmlkdWFscyBpbiB0aGUgZGF0YS4gSSBleGNsdWRlIHRoZXNlIG1hc2tlZCB2YWx1ZXMgZnJvbSB0aGUgZGF0YSBzZXQsIGFzIHdlbGwgYXMgYW55IHNjaG9vbCB0aGF0IHJlcG9ydHMgbWlzc2luZyBkYXRhIGZvciBhbGwgdGhyZWUgc3R1ZGVudCBncm91cHMuIERlbm9taW5hdG9ycyBvZiB0aGUgc2Vjb25kIHNtYWxsZXN0IHN0dWRlbnQgZ3JvdXAgZm9yIGVhY2ggc2Nob29sIGFyZSBhbHNvIG1hc2tlZCB3aXRoIGEgdW5pcXVlIHBsYWNlaG9sZGVyICgiLTMiKSB3aGVuIHRoZSBzbWFsbGVzdCBncm91cCByZXF1aXJlcyBtYXNraW5nLiBJIHJlcGxhY2UgdGhpcyBzZXQgb2YgbWFza2VkIHZhbHVlcyB3aXRoIGEgcmVhc29uYWJsZSBwcm94eSBvZiBoYWxmIHRoZSBzaXplIG9mIHRoZSBuZXh0IGxhcmdlc3QgZ3JvdXAgaW4gdGhlIGRhdGEuICANCg0KVGhpcyBjb2RlIGRvZXMgdGhlIGZvbGxvd2luZzogIA0KDQoxLiBSZWFkIGluIHN1YnNldHRlZCBUQVBSIERhdGENCjIuIEZpbHRlciBzY2hvb2xzIHdpdGggYWxsIG1pc3NpbmcgdmFsdWVzDQozLiBQaXZvdCB0aGUgZGF0YSB0byBpbmNsdWRlICh1cCB0bykgdGhyZWUgb2JzZXJ2YXRpb25zIHBlciBzY2hvb2wsIGNvcnJlc3BvbmRpbmcgdG8gdGhlIHRocmVlIHBlcmNlbnRpbGUgc2NvcmVzIGBtZWV0c19sZXZlbF9wY3RgIG9mIHN0dWRlbnRzIGluIGVhY2ggcmFjaWFsL2V0aG5pYyBjYXRlZ29yeSB0aGF0IG1lZXQgdGhlIGdyYWRlLWxldmVsIGV4cGVjdGF0aW9ucy4NCjQuIENyZWF0ZSBjYXRlZ29yeSBuYW1lcyBmb3Igc3R1ZGVudCBncm91cHM6IGBTdHVkZW50IEdyb3VwYCBhbmQgYGdyb3VwX2Rlbm9tYA0KNS4gUmVtb3ZlIG1hc2tlZCBkYXRhIGZvciBwZXJjZW50aWxlcyBhbmQgcmVjb2RlIHRoZSBtYXNrZWQgZGVub21pbmF0b3Igd2l0aCB0aGUgcHJveHkuDQo2LiBDcmVhdGUgYW4gYWRkaXRpb25hbCB2YXJpYWJsZSBgbG5fbWVldHNfZGVub21gIG9mIGxvZ2dlZCBncm91cCBzaXplIGZvciB1c2UgYXMgd2VpZ2h0IGFuZCBzaXplIGluIHRoZSBwbG90cy4gIA0KDQpUaGUgcmVzdWx0IGlzIA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmNhbXB1c18yMDE5IDwtIHJlYWQuY3N2KCJkb2NzL1RBUFJfMjAxOV9zdWJzZXQuY3N2IikgJT4lDQogIHRyYW5zbXV0ZSgNCiAgICBjaWQgPSBDQU1QVVMsDQogICAgYEFmcmljYW4gQW1lcmljYW5gID0gQ0RCMDBBMDAxMjE5UiwNCiAgICBgSGlzcGFuaWNgID0gQ0RIMDBBMDAxMjE5UiwNCiAgICBgV2hpdGVgID0gQ0RXMDBBMDAxMjE5UiwNCiAgICBhYV90b3RhbCA9IENEQjAwQTAwMTAxOUQsDQogICAgaF90b3RhbCA9IENESDAwQTAwMTAxOUQsDQogICAgd190b3RhbCA9IENEVzAwQTAwMTAxOUQpICU+JQ0KICAjIEZpbHRlciBvdXQgc2Nob29scyB3aXRoIG5vIGRhdGEgKH4gMTAlIG9mIGNhbXB1c2VzKQ0KICBmaWx0ZXIoIWlzLm5hKGBBZnJpY2FuIEFtZXJpY2FuYCkgfCAhaXMubmEoYEhpc3BhbmljYCkgfCAhaXMubmEoYFdoaXRlYCkpDQoNCmRhdGEgPC0gY2JpbmQoDQogIHBpdm90X2xvbmdlcihjYW1wdXNfMjAxOSAlPiUgc2VsZWN0KDE6NCksIGNvbHMgPSAyOjQsIG5hbWVzX3RvID0gIlN0dWRlbnQgR3JvdXAiLCB2YWx1ZXNfdG8gPSAiTWVldHMgR3JhZGUgTGV2ZWwgKCUpIiksDQogIHBpdm90X2xvbmdlcihjYW1wdXNfMjAxOSAlPiUgc2VsZWN0KDU6NyksIGNvbHMgPSAxOjMsIG5hbWVzX3RvID0gImdyb3VwX2Rlbm9tIiwgdmFsdWVzX3RvID0gIlNpemUgb2YgU3R1ZGVudCBHcm91cCBhdCBTY2hvb2wiKSkgJT4lDQogIGdyb3VwX2J5KGNpZCkgJT4lDQogIG11dGF0ZSgNCiAgICBgTWVldHMgR3JhZGUgTGV2ZWwgKCUpYCA9IGlmZWxzZShgTWVldHMgR3JhZGUgTGV2ZWwgKCUpYCA9PSAtMSwgTkEsIGBNZWV0cyBHcmFkZSBMZXZlbCAoJSlgKSwgIyByYXRlID0gLTEgaXMgbWFza2VkIGRhdGEgYW5kIHVudXNhYmxlDQogICAgYFNpemUgb2YgU3R1ZGVudCBHcm91cCBhdCBTY2hvb2xgID0gaWZlbHNlKGBTaXplIG9mIFN0dWRlbnQgR3JvdXAgYXQgU2Nob29sYCA9PSAtMSwgTkEsICMgZGVub21pbmF0b3IgPSAtMSBpcyBtYXNrZWQgZGF0YSBhbmQgdW51c2FibGUNCiAgICAgICAgICAgICAgICAgICAgIGBTaXplIG9mIFN0dWRlbnQgR3JvdXAgYXQgU2Nob29sYCksIA0KICAgIGBTaXplIG9mIFN0dWRlbnQgR3JvdXAgYXQgU2Nob29sYCA9IGlmZWxzZShgU2l6ZSBvZiBTdHVkZW50IEdyb3VwIGF0IFNjaG9vbGAgPT0gLTMsICMgZGVub21pbmF0b3IgPSAtMyBpcyBzZWNvbmQgc21hbGxlc3QgZ3JvdXANCiAgICAgICAgICAgICAgICAgICAgIG50aChgU2l6ZSBvZiBTdHVkZW50IEdyb3VwIGF0IFNjaG9vbGAsIDIsIG9yZGVyX2J5ID0gYFNpemUgb2YgU3R1ZGVudCBHcm91cCBhdCBTY2hvb2xgKSAvIDIsIA0KICAgICAgICAgICAgICAgICAgICAgYFNpemUgb2YgU3R1ZGVudCBHcm91cCBhdCBTY2hvb2xgKSwgIyByZWNvdmVyIHdpdGggcmVhc29uYWJsZSBwcm94eSBvZiBoYWxmIGxhcmdlc3QgZ3JvdXANCiAgICBsbl9tZWV0c19kZW5vbSA9IGxvZyhgU2l6ZSBvZiBTdHVkZW50IEdyb3VwIGF0IFNjaG9vbGApKSAlPiUgIyBsb2cgd2lsbCBiZSBiZXR0ZXIgZm9yIHNpemUgcGFyYW1ldGVyDQogIHVuZ3JvdXAoKQ0KDQoNCmRhdGFbMTo5LF0gJT4lDQogIGtibChjYXB0aW9uID0gIlN0cnVjdHVyZSBvZiB0aGUgRGF0YSBTZXQ6IFNob3dpbmcgdGhlIEZpcnN0IFRocmVlIFNjaG9vbHMiKSAlPiUNCiAga2FibGVfcGFwZXIoImhvdmVyIiwgZnVsbF93aWR0aCA9IEYpDQoNCiAgICAgICAgICAgICAgIA0KYGBgDQoNCiMjIENhbXB1cy1MZXZlbCBQb3ZlcnR5IERhdGENCg0KRm9yIGNhbXB1cy1sZXZlbCBwb3ZlcnR5IHN0YXRpc3RpY3MsIEkgdXNlIHRoZSBwdWJsaWMgZGF0YSBhdmFpbGFibGUgYXQgdGhlIFRleGFzIEVkdWNhdGlvbiBBZ2VuY3kncyAoVEVBKSBbc2Nob29sIGRhdGEgdmlzdWFsaXplcl0oaHR0cHM6Ly9ycHRzdnIxLnRlYS50ZXhhcy5nb3YvcGVyZnJlcG9ydC9hY2NvdW50L3ZhL3ZhX2NvcnJlbGF0ZS5odG1sKSwgd2hpY2ggaW5jbHVkZXMgYW4gZXh0ZW5zaXZlIHNldCBvZiBjYW1wdXMtbGV2ZWwgYXR0cmlidXRlcy4gSSBhbSBwcmltYXJpbHkgaW50ZXJlc3RlZCBpbiB0aGUgcGVyY2VudGFnZSBvZiBzdHVkZW50cyBmcm9tIGVjb25vbWljYWxseSBkaXNhZHZhbnRhZ2VkIGJhY2tncm91bmRzLCBtZWFzdXJlZCBhcyB0aGUgcGVyY2VudGFnZSBvZiBzdHVkZW50cyAiLi4uU3R1ZGVudHMgZWxpZ2libGUgZm9yIGZyZWUgb3IgcmVkdWNlZC1wcmljZSBsdW5jaCBvciBvdGhlciBwdWJsaWMgYXNzaXN0YW5jZSBhcyByZXBvcnRlZCBvbiB0aGUgUEVJTVMgT2N0b2JlciBzbmFwc2hvdCIgKFtzZWUgZGVmaW5pdGlvbnNdKGh0dHBzOi8vcnB0c3ZyMS50ZWEudGV4YXMuZ292L3BlcmZyZXBvcnQvZmFxc2l0ZS9nbG9zc2FyeS5odG1sKSkuIFVuZm9ydHVuYXRlbHksIHRoZSBzb3VyY2UgZG9lcyBub3QgcmV2ZWFsIHByZWNpc2VseSB3aGljaCB5ZWFyIHRoaXMgZGF0YSBjb21lcyBmcm9tISBTY2hvb2wtbGV2ZWwgcG92ZXJ0eSBpcyBhIHJlbGF0aXZlbHkgc3RhYmxlIGF0dHJpYnV0ZSwgdGhvdWdoLCBzbyB0aGlzIGlzIG5vdCBhIGh1Z2UgY29uY2Vybi4gIA0KDQpUaGUgY2hhbGxlbmdlIGlzIHRoYXQgdGhpcyBkYXRhIGRvZXMgbm90IGluY2x1ZGUgdGhlIGNhbXB1cyBJRCBhcyBhIHN0YW5kLWFsb25lIHZhcmlhYmxlLiBJIG11c3QgZmlyc3QgZXh0cmFjdCBpdCBmcm9tIGEgbG9uZ2VyIHN0cmluZyB0byBjcmVhdGUgYSBjb21wYXJhYmxlIGBjaWRgIHZhcmlhYmxlIHRoYXQgY2FuIGJlIHVzZWQgdG8gam9pbiB0aGlzIGRhdGEgc2V0IHRvIHRoZSBmaXJzdCBvbmUuIEJlY2F1c2UgdGhlcmUgaXMgbm8gdW5pdmVyc2FsIHBhdHRlcm4gdG8gcGFyc2UgdGhpcyBzdHJpbmcsIEkgaGF2ZSB0byBzcGxpdCBvbiB0aHJlZSBkaWZmZXJlbnQgcGF0dGVybnMgYW5kIGV4dHJhY3QgdGhlIHJpZ2h0bW9zdCBlbGVtZW50IGluIG9yZGVyIHRvIHJlY292ZXIgYWxsIHRoZSBjYW1wdXMgaWRlbnRpZmllcnMuIFRoaXMgaXMgYmVjYXVzZSBzb21lIHNjaG9vbHMgaGF2ZSBtb3JlIHRoYW4gb25lIHNldCBvZiB0aGUgIiAoIiBwYXR0ZXJuIG9uIHRoZSByaWdodC1oYW5kIHNpZGUgb2YgdGhlICJ8fCIgcGF0dGVybi4gRWFjaCANCg0KDQpgYGB7cn0NCnNjaG9vbCA8LSByZWFkX2V4Y2VsKCJkb2NzL3NjaG9vbC54bHN4IikNCnNjaG9vbCA8LSBzY2hvb2wgJT4lDQogIHRyYW5zbXV0ZSgNCiAgICBDYW1wdXMgPSBgQ2FtcHVzIG9yIERpc3RyaWN0YCwgDQogICAgYEVjby4gRGlzYWR2YW50YWdlZCAoJSlgID0gYCUgRWNvIERpc2FkdmFudGFnZWRgLA0KICAgIGBDYW1wdXMgVHlwZWAgPSBmYWN0b3IoYEVudGl0eSBUeXBlYCksDQogICAgZW5yb2xsbWVudF9wdWJsaWMgPSBFbnJvbGxtZW50KSAlPiUNCiAgbXV0YXRlKA0KICAgIG4gPSBzdHJfc3BsaXQoQ2FtcHVzLCAiIFxcfFxcfCIsIHNpbXBsaWZ5ID0gVFJVRSlbLDJdLCAjIEZpcnN0IHNwbGl0IG9uICJ8fCINCiAgICBubiA9IHN0cl9zcGxpdChuLCAiIFxcKCIsIHNpbXBsaWZ5ID0gVFJVRSlbLDJdLCAjIE5leHQgc3Bsb3Qgb24gIiAoIg0KICAgIG5ubiA9IHN0cl9zcGxpdChuLCAiIFxcKCIsIHNpbXBsaWZ5ID0gVFJVRSlbLDNdLCAjIEEgZmV3IGhhdmUgYW4gYWRkaXRpb25hbCAiICgiDQogICAgY2lkID0gYXMubnVtZXJpYyhnc3ViKCJcXEQrIiwgIiIsIG5uKSksDQogICAgY2lkID0gaWZlbHNlKGlzLm5hKGNpZCksIGFzLm51bWVyaWMoZ3N1YigiXFxEKyIsICIiLCBubm4pKSwgY2lkKSkgJT4lDQogIHNlbGVjdCgtc3RhcnRzX3dpdGgoIm4iKSkNCg0Kc2Nob29sWzE6NSxdICU+JQ0KICBrYmwoY2FwdGlvbiA9ICJTdHJ1Y3R1cmUgb2YgdGhlIFBvdmVydHkgRGF0YSBhZnRlciBSZWNvdmVyaW5nIHRoZSBDYW1wdXMgSWRlbnRpZmllciIpICU+JQ0KICBrYWJsZV9wYXBlcigiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikNCg0KYGBgDQoNCiMgQ29kZXM6IFNjaG9vbCwgRGlzdHJpY3QsIFJlZ2lvbiwgQ291bnR5DQoNClRoaXMgZGF0YSBzZXQgZnJvbSBURUEgaW5jbHVkZXMgdGhlIGNvZGVzIHRoYXQgY2FuIGJlIHVzZWQgZm9yIGdlb2dyYXBoaWMgaWRlbnRpZmljYXRpb24gaW4gdGhlIHBsb3RzLg0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQpjb2RlcyA8LSByZWFkX2NzdigiZG9jcy9zY2hvb2wgYW5kIGRpc3RyaWN0LmNzdiIpDQpjb2RlcyA8LSBjb2RlcyAlPiUNCiAgdHJhbnNtdXRlKA0KICAgIGNpZCA9IGFzLm51bWVyaWMoZ3N1YigiW15bOmFsbnVtOl0gXSIsICIiLCBgU2Nob29sIE51bWJlcmApKSwNCiAgICBkaXN0cmljdCA9IGFzLm51bWVyaWMoZ3N1YigiW15bOmFsbnVtOl0gXSIsICIiLCBgRGlzdHJpY3QgTnVtYmVyYCkpLA0KICAgIGNvdW50eSA9IGFzLm51bWVyaWMoZ3N1YigiW15bOmFsbnVtOl0gXSIsICIiLCBgQ291bnR5IE51bWJlcmApKSwNCiAgICByZWdpb24gPSBhcy5udW1lcmljKGdzdWIoIlteWzphbG51bTpdIF0iLCAiIiwgYEVTQyBSZWdpb24gU2VydmVkYCkpKQ0KDQpgYGANCg0KDQoNCiMjIEpvaW4gRGF0YSBhbmQgU2VlIERlc2NyaXB0aXZlIFN0YXRpc3RpY3MNCg0KVGFraW5nIHRoZSBUQVBSIDIwMTkgZGF0YSBhcyB0aGUgYmFzZSBzZXQsIEkgbWF0Y2ggYXBwcm94aW1hdGVseSA5OC4zJSBvZiBjYW1wdXNlcyB3aXRoIHRoZSBURUEgc2Nob29sLWxldmVsIGRhdGEgYnkgdGhlaXIgY2FtcHVzIGlkIChgY2lkYCkuIEZvciBwYXJzaW1vbnksIEkgZXhjbHVkZSB0aG9zZSAlMS43JSBjYW1wdXNlcyB0aGF0IGRvIG5vdCBleGlzdCBpbiBib3RoIGRhdGEgc2V0cy4gV2UgY2FuIHNlZSB0aGF0IHRoZSBtYXNraW5nIGFuZCBtaXNzaW5nIGRhdGEgcmVzdWx0cyBpbiBhcHByb3hpbWF0ZWx5IDEwJSBvZiBtaXNzaW5nIGRhdGEgZnJvbSB0aGUgVEFQUiAyMDE5IGRhdGE7IGhvd2V2ZXIsIGR1ZSB0byB0aGUgbG9naWMgb2YgaG93IHRoZSBtYXNraW5nIGlzIGNvZGVkLCB0aG9zZSBtYXNrZWQgb2JzZXJ2YXRpb25zIGFyZSBsaWtlbHkgdG8gYmUgc21hbGxlciwgcmVsYXRpdmVseSBtb3JlIGhvbW9nZW5lb3VzIHNjaG9vbHMsIG90aGVyd2lzZSwgdGhleSB3b3VsZCBub3QgaGF2ZSB2ZXJ5IHNtYWxsIGFic29sdXRlIG51bWJlcnMgb2Ygc3R1ZGVudHMgZnJvbSBhbnkgb2YgdGhlIHRocmVlIHJhY2lhbC9ldGhuaWMgYmFja2dyb3VuZHMuICANCg0KYGBge3IsIHJlc3VsdHMgPSAiYXNpcyJ9DQojY2FtcHVzIDwtIGxlZnRfam9pbihkYXRhLCBzY2hvb2wsIGJ5ID0gImNpZCIpICU+JSBsZWZ0X2pvaW4oY29kZXMsIGJ5ID0gImNpZCIpDQpjYW1wdXMgPC0gbGVmdF9qb2luKHNjaG9vbCwgY29kZXMsIGJ5ID0gImNpZCIpICU+JSByaWdodF9qb2luKGRhdGEsIGJ5ID0gImNpZCIpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKENhbXB1cykpDQpkZlN1bW1hcnkoY2FtcHVzLA0KICAgICAgICAgIHBsYWluLmFzY2lpICA9IEZBTFNFLCANCiAgICAgICAgICBzdHlsZSAgICAgICAgPSAiZ3JpZCIsIA0KICAgICAgICAgIGdyYXBoLm1hZ25pZiA9IDAuNzUsIA0KICAgICAgICAgIHZhbGlkLmNvbCAgICA9IEZBTFNFLA0KICAgICAgICAgIHRtcC5pbWcuZGlyICA9ICIvdG1wIiwNCiAgICAgICAgICBzaWxlbnQgPSBUUlVFKQ0KYGBgDQoNCg0KIyMgRXhwbG9yaW5nIHRoZSBSZWxhdGlvbnNoaXAgYmV0d2VlbiBSYWNlL0V0aG5pY2l0eSwgUG92ZXJ0eSwgYW5kIEVkdWNhdGlvbiBPdXRjb21lcw0KDQpIZXJlIEkgd3JpdGUgYSBmdW5jdGlvbiB0byBidWlsZCBgcGxvdGx5YCBpbnRlcmFjdGl2ZSB2aXN1YWxpemF0aW9ucyAoaG92ZXIgb3ZlciB0aGUgaW1hZ2UgdG8gZ2V0IHBvaW50LWxldmVsIGRhdGEpLiBTY2hvb2wtbGV2ZWwgZGF0YSBpcyBwbG90dGVkIG9uIHRoZSB4LWF4aXMgYmFzZWQgb24gcG92ZXJ0eSBsZXZlbHMgYEVjby4gRGlzYWR2YW50ZWQgKCUpYC4gU2Nob29sLWxldmVsIGRhdGEgZGlzYWdncmVnYXRlZCBieSByYWNlL2V0aG5pY2l0eSBvbiB0aGUgeS1heGlzIHRvIHNob3cgZWFjaCByYWNpYWwvZXRobmljIGdyb3VwcydzIGFjYWRlbWljIHBlcmZvcm1hbmNlIGFzIGBNZWV0cyBHcmFkZSBMZXZlbCAoJSlgLiBJIHdyaXRlIGEgY3VzdG9tIGZ1bmN0aW9uIHRoYXQgdGFrZXMgdGhlIGRhdGEgYW5kIHR3byBmaWx0ZXJpbmcgcGFyYW1ldGVycyB0byBzbGljZSB0aGUgZnVsbCBkYXRhIHNldDogYGNhbXB1cyAlPiUgZmlsdGVyKCdDYW1wdXMgVHlwZScgPT0ge3t0eXBlfX0gJiByZWdpb24gPT0ge3tyfX0pYC4gSSBwYXNzIHRoaXMgZnVuY3Rpb24gdG8gYGxhcHBseSgpYCB0byBnZW5lcmF0ZSBhbnkgb3IgYWxsIGNvbWJpbmF0aW9ucyBvZiBgcmVnaW9uYCBhbmQgYENhbXB1cyBUeXBlYCAodXAgdG8gKQ0KDQpgYGB7ciwgcmVzdWx0cyA9ICJhc2lzIiwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQoNCg0KciA8LSAxOjIwDQoNCnBsb3RzIDwtIGZ1bmN0aW9uKGRhdGEsIHIsIHR5cGUpIHsNCg0KdCA8LSAgZ2dwbG90KGRhdGEgPSBjYW1wdXMgJT4lIGZpbHRlcihgQ2FtcHVzIFR5cGVgID09IHt7dHlwZX19ICYgcmVnaW9uID09IHt7cn19KSwgDQogICAgICAgICBhZXMobGFiZWwgPSBgQ2FtcHVzYCwgbGFiZWwyID0gYFNpemUgb2YgU3R1ZGVudCBHcm91cCBhdCBTY2hvb2xgLCB4ID0gYEVjby4gRGlzYWR2YW50YWdlZCAoJSlgLCB5ID0gYE1lZXRzIEdyYWRlIExldmVsICglKWApKSArDQogIGdlb21fbGluZShhbHBoYSA9IC4zLCBhZXMoZ3JvdXAgPSBjaWQpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAuNCwgYWVzKHNpemUgPSBgU2l6ZSBvZiBTdHVkZW50IEdyb3VwIGF0IFNjaG9vbGAsIGNvbG9yID0gYFN0dWRlbnQgR3JvdXBgKSkgKw0KICBnZW9tX3Ntb290aChzaXplID0gMiwgbWV0aG9kID0gbG9lc3MsIGNvbG9yID0gImJsYWNrIiwgc2UgPSBGQUxTRSwgYWVzKGdyb3VwID0gYFN0dWRlbnQgR3JvdXBgKSkgKw0KICBnZW9tX3Ntb290aChzaXplID0gMS44LCBtZXRob2QgPSBsb2Vzcywgc2UgPSBGQUxTRSwgYWVzKGNvbG9yID0gYFN0dWRlbnQgR3JvdXBgKSkgKw0KICBndWlkZXMoc2l6ZSA9IEZBTFNFKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArDQogIHhsaW0oYygwLDEwMCkpICsgeWxpbShjKDAsMTAwKSkgKyANCiAgICBnZ3RpdGxlKHBhc3RlKCJSZWdpb24iLCB7e3J9fSwge3t0eXBlfX0sICJQZXJmb3JtYW5jZSBieSBSYWNlL0V0aG5pY2l0eSBhbmQgUG92ZXJ0eSIsIHNlcCA9ICIgIikpDQogIA0KdFtbcl1dIDwtIGdncGxvdGx5KHQsIHRvb2x0aXAgPSBjKCJsYWJlbCIsICJsYWJlbDIiLCAieCIsICJ5IikpDQoNCiNwcmludCh0W1tyXV0pDQojcmV0dXJuKHRbW3JdXSkNCn0NCg0KIyBUaGlzIHdvcmtzIGluIFIgYnV0IG5vdCBmb3IgbWFya2Rvd24gdG8gaHRtbA0KIyBmb3IgKGkgaW4gMToyMCl7DQojICAgcGxvdHMoY2FtcHVzLCBpKQ0KIyB9DQoNCmdnX2hzIDwtIGxhcHBseShyLCBwbG90cywgZGF0YSA9IGNhbXB1cywgdHlwZSA9ICJIaWdoIFNjaG9vbCIpDQoNCmdnX2VzIDwtIGxhcHBseShyLCBwbG90cywgZGF0YSA9IGNhbXB1cywgdHlwZSA9ICJFbGVtZW50YXJ5IFNjaG9vbCIpDQoNCg0KDQpgYGANCg0KDQpgYGB7ciwgcmVzdWx0cyA9ICJhc2lzIiwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQoNCmdnX2VzDQoNCmdnX2hzW1sxXV0NCg0Kc3VicGxvdChnZ19oc1tbMTBdXSwgZ2dfZXNbWzEwXV0sIGdnX2hzW1syMF1dLCBnZ19lc1tbMjBdXSwgbnJvd3MgPSAyKQ0KDQpodG1sdG9vbHM6OnRhZ0xpc3QobGlzdChnZ19oc1tbMTBdXSwgZ2dfZXNbWzEwXV0sIGdnX2hzW1syMF1dLCBnZ19lc1tbMjBdXSkpDQpodG1sdG9vbHM6OnRhZ0xpc3QobGlzdChnZ19oc1tbMV1dLCBnZ19oc1tbM11dLCBnZ19lc1tbMV1dLCBnZ19lc1tbM11dKSkNCg0KYGBgDQoNCg0KIyBQcml2YXRlIFNjaG9vbHMgRGF0YSAyMDE4IC0gMjAxOQ0KDQpgYGB7cn0NCnByaXZhdGUgPC0gIHJlYWRfZXhjZWwoImRvY3MvcHJpdmF0ZSBzY2hvb2xzIDIwMTgtMjAxOS54bHN4IikNCnByaXZhdGUgPC1wcml2YXRlICU+JQ0KICBmaWx0ZXIoIWBHcmFkZSBIaWdoYCAlaW4lIGMoIlByZS1LIiwgIkVhcmx5IEVkdWNhdGlvbiIpICYgQ2xvc2VkID09IEZBTFNFKSAlPiUNCiAgdHJhbnNtdXRlKA0KICAgIGRpc3RyaWN0ID0gYXMubnVtZXJpYyhgRGlzdHJpY3QgTnVtYmVyYCksDQogICAgY291bnR5ID0gYXMubnVtZXJpYyhgQ291bnR5IE51bWJlcmApLA0KICAgIHJlZ2lvbiA9IGFzLm51bWVyaWMoYFJlZ2lvbiBOYW1lYCksDQogICAgZW5yb2xsbWVudF9wcml2YXRlID0gYXMubnVtZXJpYyhFbnJvbGxtZW50KSkNCg0KcHJpdmF0ZV9yZWdpb24gPC0gcHJpdmF0ZSAlPiUNCiAgZ3JvdXBfYnkocmVnaW9uKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIHByaXZhdGVfZW5yb2xsbWVudF9yZWdpb24gPSBzdW0oZW5yb2xsbWVudF9wcml2YXRlLCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgdW5ncm91cCgpDQoNCnB1YmxpY19yZWdpb24gPC0gc2Nob29sICU+JQ0KICBsZWZ0X2pvaW4oY29kZXMsIGJ5ID0gImNpZCIpICU+JQ0KICBncm91cF9ieShyZWdpb24pICU+JQ0KICBzdW1tYXJpc2UoDQogICAgcHVibGljX2Vucm9sbG1lbnRfcmVnaW9uID0gc3VtKGVucm9sbG1lbnRfcHVibGljLCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgdW5ncm91cCgpDQogIA0KcmVnaW9uIDwtIGxlZnRfam9pbihwcml2YXRlX3JlZ2lvbiwgcHVibGljX3JlZ2lvbiwgYnkgPSAicmVnaW9uIikgDQpyZWdpb24gPC0gcmVnaW9uICU+JQ0KICBtdXRhdGUoDQogICAgYFRvdGFsIEVucm9sbG1lbnRgID0gcHJpdmF0ZV9lbnJvbGxtZW50X3JlZ2lvbiArIHB1YmxpY19lbnJvbGxtZW50X3JlZ2lvbiwNCiAgICBgUHJpdmF0ZSBTdHVkZW50cyBpbiBSZWdpb24gKCUpYCA9IDEwMCAqICggcHJpdmF0ZV9lbnJvbGxtZW50X3JlZ2lvbiAvIGBUb3RhbCBFbnJvbGxtZW50YCksDQogICAgUmVnaW9uX2xhYiA9IHBhc3RlKCJSZWdpb24iLCByZWdpb24sIHNlcCA9ICIgIiksDQogICAgUmVnaW9uID0gZmFjdG9yKHJlZ2lvbiwgb3JkZXJlZCA9IFRSVUUsIGxhYmVscyA9IFJlZ2lvbl9sYWIpKSAlPiUNCiAgYXJyYW5nZShkZXNjKGBQcml2YXRlIFN0dWRlbnRzIGluIFJlZ2lvbiAoJSlgKSkgJT4lDQogIG11dGF0ZSgNCiAgICBSZWdpb25fbGFiID0gcGFzdGUoIlJlZ2lvbiIsIHJlZ2lvbiwgc2VwID0gIiAiKSwNCiAgICBgUmVnaW9uIE9yZGVyZWRgID0gZmFjdG9yKHJlZ2lvbiwgbGV2ZWxzID0gcmVnaW9uLCBsYWJlbHMgPSBwYXN0ZSgiUmVnaW9uIiwgcmVnaW9uLCBzZXAgPSAiICIpKSkNCg0KI3JtKHByaXZhdGUsIHByaXZhdGVfcmVnaW9uLCBwdWJsaWNfcmVnaW9uKQ0KDQpnZ3Bsb3RseShnZ3Bsb3QocmVnaW9uLCBhZXMoeCA9IGxvZyhgVG90YWwgRW5yb2xsbWVudGApLCB5ID0gYFByaXZhdGUgU3R1ZGVudHMgaW4gUmVnaW9uICglKWAsIGxhYmVsID0gcmVnaW9uKSkgKw0KICBnZW9tX3Ntb290aChhbHBoYSA9IC40LCBtZXRob2QgPSBsbSkgKw0KICAgIGdlb21fdGV4dChzaXplID0gNSkpDQpnZ3Bsb3RseShnZ3Bsb3QocmVnaW9uICU+JSBmaWx0ZXIoIXJlZ2lvbiAlaW4lIGMoMSwgMykpLCBhZXMoeCA9IGxvZyhgVG90YWwgRW5yb2xsbWVudGApLCB5ID0gYFByaXZhdGUgU3R1ZGVudHMgaW4gUmVnaW9uICglKWAsIGxhYmVsID0gcmVnaW9uKSkgKw0KICBnZW9tX3Ntb290aChhbHBoYSA9IC40LCBtZXRob2QgPSBsbSkgKw0KICAgIGdlb21fdGV4dChzaXplID0gNSkpDQoNCnAxIDwtIGdncGxvdGx5KGdncGxvdChyZWdpb24sIGFlcyh4ID0gYFByaXZhdGUgU3R1ZGVudHMgaW4gUmVnaW9uICglKWAsIHkgPSBSZWdpb24pKSArDQogIGdlb21fY29sKCkpDQoNCnAyIDwtIGdncGxvdGx5KGdncGxvdChyZWdpb24sIGFlcyh4ID0gYFByaXZhdGUgU3R1ZGVudHMgaW4gUmVnaW9uICglKWAsIHkgPSBgUmVnaW9uIE9yZGVyZWRgKSkgKw0KICBnZW9tX2NvbCgpKQ0KDQpzdWJwbG90KHAxLCBwMiwgbWFyZ2luID0gMC4wNykNCg0KYGBgDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=